
{***********************************************************}
{                                                           }
{      Luxena Database Library                              }
{                                                           }
{      DataSet Implementor                                  }
{                                                           }
{      Copyright (c) 2001-2008 Luxena Software Company      }
{      http://www.luxena.com                                }
{      e-mail: support@luxena.com                           }
{                                                           }
{***********************************************************}

unit LXDataSetImpl;

{$I lx.inc}

interface

uses
  Classes, DB, LXTypes, LXRecordAllocator, LXSQLProcessor;


type

{ Enumerations }

  TLXRecordUpdateOption = (uoCheckRowsAffected, uoAllowMultiRecordUpdates,
    uoAllowAutomaticUpdate, uoUpdateInTransaction);

  TLXRecordRefreshMode = (rmNone, rmInserted, rmPosted);


const

{ Property Default Values }

  LX_DEFAULT_DESIGN_ACTIVATION = False;
  LX_DEFAULT_BUFFER_CHUNKS = 32;
  LX_DEFAULT_PACKET_RECORDS = 1;
  LX_DEFAULT_UNIDIRECTIONAL = False;
  LX_DEFAULT_UPDATE_OPTIONS = [uoCheckRowsAffected, uoAllowAutomaticUpdate];
  LX_DEFAULT_UPDATE_MODE = upWhereAll;
  LX_DEFAULT_REFRESH_LOCATES = True;
  LX_DEFAULT_RECORD_REFRESH_MODE = rmNone;
  LX_DEFAULT_CACHED_UPDATES = False;
  LX_DEFAULT_AUTO_COMMIT_UPDATES = True;


type

{ Forward declarations }

  PLXRecordBuffer = ^TLXRecordBuffer;
  TLXDataFieldManager = class;
  TLXDataSetImpl = class;
  TLXDataSetUpdateObject = class;

{$IFNDEF D11}
  TRecordBuffer = PChar;
{$ENDIF}


{ ILXDataSetSupport }

  ILXDataSetSupport = interface
    ['{09B247B9-F123-4840-A1BA-93FAA0546608}']
    function CreateFieldManager(Field: TField): TLXDataFieldManager;
    function CustomDataSize: Integer;
    function FetchNext: TGetResult;
    function ReadField(Field: TField; Buffer: PChar): Boolean;
    procedure ReadCustomData(Buffer: PChar);
    procedure Restart;
  end;


{ ILXUpdateSQLSupport }

  ILXUpdateSQLSupport = interface
    ['{7B0F476B-AA83-4BF6-93A5-680167A4FA7B}']
    function CreateSQLGenerator(Delta: TDataSet): TLXSQLGenerator;
    function ModifyRecord(Delta: TDataSet; UpdateKind: TUpdateKind; const SQL: string): Integer;
    procedure RefreshRecord(Delta: TDataSet; UpdateKind: TUpdateKind; const SQL: string);
  end;


{ TLXFieldManager }

  TLXFieldManager = class
  public
    function DataReadBuffer(RecordBuffer: PLXRecordBuffer): PChar; virtual; abstract;
    function DataWriteBuffer(RecordBuffer: PLXRecordBuffer): PChar; virtual; abstract;
    function GetData(RecordBuffer: PLXRecordBuffer; Buffer: PChar): Boolean; virtual; abstract;
    procedure SetData(RecordBuffer: PLXRecordBuffer; Buffer: PChar); virtual; abstract;
  end;


{ TLXDataFieldManager }

  PLXBlobData = ^TLXBlobData;
  TLXBlobData = string;

  TLXDataFieldManager = class(TLXFieldManager)
  protected
    FField: TField;
    FOffset: Integer;
  public
    constructor Create(Field: TField);
    function SetOffset(Value: Integer): Integer; virtual;
    procedure ReadData(const DataSetSupport: ILXDataSetSupport;
      RecordBuffer: PLXRecordBuffer); virtual;
    function DataReadBuffer(RecordBuffer: PLXRecordBuffer): PChar; override;
    function DataWriteBuffer(RecordBuffer: PLXRecordBuffer): PChar; override;
    function GetData(RecordBuffer: PLXRecordBuffer; Buffer: PChar): Boolean; override;
    procedure SetData(RecordBuffer: PLXRecordBuffer; Buffer: PChar); override;
    function GetBlobData(RecordBuffer: PLXRecordBuffer): TLXBlobData; virtual;
    function CreateBlobStream(DataSetImpl: TLXDataSetImpl;
      RecordBuffer: PLXRecordBuffer; Mode: TBlobStreamMode): TStream; virtual;
    function IsModified(RecordBuffer: PLXRecordBuffer): Boolean;
    procedure SetIsModified(RecordBuffer: PLXRecordBuffer; Value: Boolean);
    procedure Copy(Source, Dest: PLXRecordBuffer); virtual;
    function RequiresCleanup: Boolean; virtual;
    procedure Cleanup(RecordBuffer: PLXRecordBuffer); virtual;
    property Field: TField read FField;
  end;


{ TLXBlobFieldManager }

  TLXBlobFieldManager = class(TLXDataFieldManager)
  public
    function SetOffset(Value: Integer): Integer; override;
    procedure ReadData(const DataSetSupport: ILXDataSetSupport;
      RecordBuffer: PLXRecordBuffer); override;
    function DataReadBuffer(RecordBuffer: PLXRecordBuffer): PChar; override;
    function DataWriteBuffer(RecordBuffer: PLXRecordBuffer): PChar; override;
    function GetData(RecordBuffer: PLXRecordBuffer; Buffer: PChar): Boolean; override;
    procedure SetData(RecordBuffer: PLXRecordBuffer; Buffer: PChar); override;
    function GetBlobData(RecordBuffer: PLXRecordBuffer): TLXBlobData; override;
    function CreateBlobStream(DataSetImpl: TLXDataSetImpl;
      RecordBuffer: PLXRecordBuffer; Mode: TBlobStreamMode): TStream; override;
    procedure Copy(Source, Dest: PLXRecordBuffer); override;
    function RequiresCleanup: Boolean; override;
    procedure Cleanup(RecordBuffer: PLXRecordBuffer); override;
  end;


{ TLXCalcFieldsManager }

  TLXCalcFieldsManager = class(TLXFieldManager)
  private
    FCalcFieldsSize: Integer;
    FCalcFieldsOffset: Integer;
    FOffset: Integer;
    FDataSize: Integer;
  public
    constructor Create(CalcFieldsSize: Integer);
    function SetOffset(Value: Integer): Integer;
    procedure SetField(Field: TField);
    function DataReadBuffer(RecordBuffer: PLXRecordBuffer): PChar; override;
    function DataWriteBuffer(RecordBuffer: PLXRecordBuffer): PChar; override;
    function GetData(RecordBuffer: PLXRecordBuffer; Buffer: PChar): Boolean; override;
    procedure SetData(RecordBuffer: PLXRecordBuffer; Buffer: PChar); override;
    procedure Copy(Source, Dest: PLXRecordBuffer);
    procedure Cleanup(RecordBuffer: PLXRecordBuffer);
  end;


{ TLXBlobStream }

  TLXBlobStream = class(TStringStream)
  private
    FDataSetImpl: TLXDataSetImpl;
    FFieldManager: TLXBlobFieldManager;
    FRecordBuffer: PLXRecordBuffer;
    FMode: TBlobStreamMode;
    FModified: Boolean;
    FDataString: string;
    procedure CheckWriteMode;
  protected
    procedure SetSize(NewSize: Longint); override;
  public
    constructor Create(DataSetImpl: TLXDataSetImpl;
      FieldManager: TLXBlobFieldManager; RecordBuffer: PLXRecordBuffer;
      Mode: TBlobStreamMode);
    destructor Destroy; override;
    function Write(const Buffer; Count: Longint): Longint; override;
  end;


{ TLXDataSetImpl }

  TLXDataSetChild = class(TDataSet);

  PPRecordBuffer = ^PLXRecordBuffer;
  TLXRecordBuffer = packed record
    Next: PLXRecordBuffer;
    Prior: PLXRecordBuffer;
    UpdateStatus: TUpdateStatus;
    Backup: PLXRecordBuffer;
    SequenceNumber: Integer;
  end;

  TLXRecordUpdateOptions = set of TLXRecordUpdateOption;

  TLXGetTableNameEvent = procedure (DataSet: TDataSet; var TableName: string) of object;

  TLXUpdateAppliedEvent = procedure (DataSet: TDataSet; UpdateKind: TUpdateKind;
    var RefreshMode: TLXRecordRefreshMode) of object;

  TLXDataSetStateContext = record
    State: TDataSetState;
    Modified: Boolean;
  end;

  TLXDataSetImpl = class

  { Construction / Destruction }
  private
    FDataSet: TLXDataSetChild;
    FDataSetSupport: ILXDataSetSupport;
    FUpdateSQLSupport: ILXUpdateSQLSupport;
    FDesignActivation: Boolean;
    procedure SetDesignActivation(Value: Boolean);
  public
    constructor Create(DataSet: TDataSet; const DataSetSupport: ILXDataSetSupport;
      const UpdateSQLSupport: ILXUpdateSQLSupport);
    destructor Destroy; override;
    property DataSet: TLXDataSetChild read FDataSet;
    property DataSetSupport: ILXDataSetSupport read FDataSetSupport;
    property UpdateSQLSupport: ILXUpdateSQLSupport read FUpdateSQLSupport;
    property DesignActivation: Boolean read FDesignActivation write SetDesignActivation;

  { Records Management }
  private
    FDataManagers: TList;
    FFieldNoToManager: array of TLXDataFieldManager;
    FCleanupManagers: TList;
    FCustomDataOffset: Integer;
    FCalcsManager: TLXCalcFieldsManager;
    FBufferChunks: Integer;
    FPacketRecords: Integer;
    FRecordSize: Integer;
    FRecords: TLXRecordAllocator;
    procedure SetBufferChunks(Value: Integer);
    procedure SetPacketRecords(Value: Integer);
    function InternalFetchNextRecord: TGetResult;
    function FetchNextRecord: TGetResult;
    procedure FillRecordFromCursor(RecordBuffer: PLXRecordBuffer);
  public
    procedure InternalOpen;
    procedure InternalClose;
    function IsCursorOpen: Boolean;
    function GetRecord(GetMode: TGetMode): TGetResult; overload;
    function GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode; DoCheck: Boolean): TGetResult; overload;
    procedure FetchAll;
    procedure Requery;
    property BufferChunks: Integer read FBufferChunks write SetBufferChunks;
    property PacketRecords: Integer read FPacketRecords write SetPacketRecords;

  { Buffer Management }
  private
    function AllocRecord: PLXRecordBuffer;
    procedure ClearRecord(RecordBuffer: Pointer);
    procedure FreeRecord(RecordBuffer: PLXRecordBuffer);
    procedure InsertRecord(RecordBuffer: PLXRecordBuffer);
    procedure DeleteRecord(RecordBuffer: PLXRecordBuffer);
    procedure CopyRecord(Source, Dest: PLXRecordBuffer);
  public
    function AllocRecordBuffer: TRecordBuffer;
    procedure FreeRecordBuffer(var Buffer: TRecordBuffer);
    procedure InitRecord(Buffer: TRecordBuffer);
    procedure ClearCalcFields(Buffer: TRecordBuffer);
    function GetCurrentRecord(Buffer: TRecordBuffer): Boolean;
    function SetDataSetState(DataSet: TDataSet; State: TDataSetState): TLXDataSetStateContext;
    procedure RestoreDataSetState(DataSet: TDataSet; Context: TLXDataSetStateContext);

  { Field Management }
  private
    function GetActiveRecordBuffer: PLXRecordBuffer;
    function GetFieldsRecordBuffer(State: TDataSetState): PLXRecordBuffer; overload;
    function GetFieldsRecordBuffer(var RecordBuffer: PLXRecordBuffer;
      State: TDataSetState): Boolean; overload;
    procedure CheckWriteModes(Field: TField);
    function GetFieldManager(Field: TField): TLXFieldManager;
  public
    function FieldDataReadBuffer(Field: TField): PChar;
    function FieldDataWriteBuffer(Field: TField): PChar;
    procedure NotifyFieldChange(Field: TField);
    function GetFieldData(Field: TField; Buffer: Pointer): Boolean; overload;
    function GetFieldData(FieldNo: Integer; Buffer: Pointer): Boolean; overload;
    function GetBlobFieldData(FieldNo: Integer; var Buffer: TBlobByteData): Integer;
    function HasFieldStateValue(State: TDataSetState; Field: TField): Boolean;
    procedure SetFieldData(Field: TField; Buffer: Pointer);
    function CreateBlobStream(Field: TField; Mode: TBlobStreamMode): TStream;
    function CustomDataBuffer: PChar;

  { Navigation }
  private
    FBOFRecord: TLXRecordBuffer;
    FCurrentRecord: PLXRecordBuffer;
    FEOFRecord: TLXRecordBuffer;
    function MoveFromCurrentRecord: PLXRecordBuffer;
    procedure SkipRecords(Value: Integer);
  public
    procedure InternalFirst;
    procedure InternalLast;

  { Editing }
  private
    FRecordBackup: PLXRecordBuffer;
    FUpdatedBuffer: PLXRecordBuffer;
    FUpdatedRecords: TList;
    FAppliedRecords: TList;
    FUpdateOptions: TLXRecordUpdateOptions;
    FUpdateMode: TUpdateMode;
    FUpdateObject: TLXDataSetUpdateObject;
    FRefreshLocates: Boolean;
    FRecordRefreshMode: TLXRecordRefreshMode;
    FCachedUpdates: Boolean;
    FAutoCommitUpdates: Boolean;
    procedure SetUpdateObject(Value: TLXDataSetUpdateObject);
    procedure SetCachedUpdates(Value: Boolean);
    function GetUpdatedRecords: TList;
    function DoModifyRecord(Delta: TDataSet; UpdateKind: TUpdateKind): Boolean;
    procedure DoUpdateApplied(Delta: TDataSet; UpdateKind: TUpdateKind);
    procedure DoUpdateError(E: EDatabaseError; UpdateKind: TUpdateKind;
      var UpdateAction: TUpdateAction);
    procedure RegisterUpdate(UpdateStatus: TUpdateStatus);
    function UnregisterUpdate: Boolean;
    procedure CommitUpdate(RecordBuffer: PLXRecordBuffer);
    procedure RollbackUpdate(RecordBuffer: PLXRecordBuffer);
    procedure ResyncAndCheckForScroll;
  protected
    function GetTableName: string;
  public
    procedure InternalInitRecord(Buffer: TRecordBuffer);
    procedure InternalAddRecord(Buffer: Pointer; Append: Boolean);
    procedure InternalInsert;
    procedure InternalEdit;
    procedure InternalDelete;
    procedure InternalPost;
    procedure InternalCancel;
    procedure InternalRestart;
    procedure InternalRefresh;
    procedure InternalEmpty;
    function UpdateRecord(UpdateKind: TUpdateKind; Delta: TDataSet): Boolean;
    procedure ApplyUpdates; overload;
    procedure ApplyUpdates(CommitUpdates: Boolean); overload;
    procedure CommitUpdates;
    procedure CancelUpdates;
    procedure RevertRecord;
    property UpdateOptions: TLXRecordUpdateOptions read FUpdateOptions write FUpdateOptions;
    property UpdateMode: TUpdateMode read FUpdateMode write FUpdateMode;
    property UpdateObject: TLXDataSetUpdateObject read FUpdateObject write SetUpdateObject;
    property RefreshLocates: Boolean read FRefreshLocates write FRefreshLocates;
    property RecordRefreshMode: TLXRecordRefreshMode read FRecordRefreshMode write FRecordRefreshMode;
    property CachedUpdates: Boolean read FCachedUpdates write SetCachedUpdates;
    property AutoCommitUpdates: Boolean read FAutoCommitUpdates write FAutoCommitUpdates;

  { Informational }
  private
{$IFNDEF D6}
    FUniDirectional: Boolean;
{$ENDIF}
    function GetUniDirectional: Boolean;
    procedure SetUniDirectional(Value: Boolean);
    function GetUpdatesPending: Boolean;
    function IsAllFetched: Boolean;
    function IsFiltered: Boolean;
  public
    function GetCanModify: Boolean;
    function GetRecordCount: Longint;
    procedure SetRecNo(Value: Integer);
    function GetRecNo: Integer;
    function GetRecordSize: Word;
    function UpdateStatus: TUpdateStatus;
    function IsSequenced: Boolean;
    property UniDirectional: Boolean read GetUniDirectional write SetUniDirectional;
    property UpdatesPending: Boolean read GetUpdatesPending;

  { Bookmarks Support }
  public
    function GetBookmarkSize: Integer;
    procedure GetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
    procedure SetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
    function GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag;
    procedure SetBookmarkFlag(Buffer: TRecordBuffer; Value: TBookmarkFlag);
    procedure InternalGotoBookmark(Bookmark: Pointer);
    procedure InternalSetToRecord(Buffer: TRecordBuffer);
    function BookmarkValid(Bookmark: TBookmark): Boolean;
    function CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Integer;

  { Filter / Locate / Find }
  private
    FFilterBuffer: PLXRecordBuffer;
    FStatusFilter: TUpdateStatusSet;
    procedure SetStatusFilter(Value: TUpdateStatusSet);
    function IsInStatusFilter(UpdateStatus: TUpdateStatus): Boolean;
    function LocateRecord(const KeyFields: string; KeyValues: Variant;
      Options: TLocateOptions; FromFirst: Boolean): Boolean;
  public
    procedure SetCurrentAsFiltered;
    function Locate(const KeyFields: string; const KeyValues: Variant;
      Options: TLocateOptions; FromFirst: Boolean): Boolean;
    function Lookup(const KeyFields: string; const KeyValues: Variant;
      const ResultFields: string): Variant;
    property StatusFilter: TUpdateStatusSet read FStatusFilter write SetStatusFilter;

  { Events }
  private
    FBeforeApplyUpdates: TDataSetNotifyEvent;
    FAfterApplyUpdates: TDataSetNotifyEvent;
    FOnUpdateRecord: TUpdateRecordEvent;
    FOnGetTableName: TLXGetTableNameEvent;
    FOnUpdateApplied: TLXUpdateAppliedEvent;
    FOnUpdateError: TUpdateErrorEvent;
  public
    property BeforeApplyUpdates: TDataSetNotifyEvent read FBeforeApplyUpdates write FBeforeApplyUpdates;
    property AfterApplyUpdates: TDataSetNotifyEvent read FAfterApplyUpdates write FAfterApplyUpdates;
    property OnUpdateRecord: TUpdateRecordEvent read FOnUpdateRecord write FOnUpdateRecord;
    property OnGetTableName: TLXGetTableNameEvent read FOnGetTableName write FOnGetTableName;
    property OnUpdateApplied: TLXUpdateAppliedEvent read FOnUpdateApplied write FOnUpdateApplied;
    property OnUpdateError: TUpdateErrorEvent read FOnUpdateError write FOnUpdateError;
  end;


{ TLXDataSetUpdateObject }

  TLXDataSetUpdateObject = class(TComponent)
  private
    FDataSetImpl: TLXDataSetImpl;
    FDataSet: TDataSet;
    procedure SetDataSetImpl(DataSetImpl: TLXDataSetImpl);
  protected
    property DataSetImpl: TLXDataSetImpl read FDataSetImpl;
    function Apply(Delta: TDataSet; UpdateKind: TUpdateKind; var RowsAffected: Integer): Boolean; virtual; abstract;
    function Refresh(Delta: TDataSet; UpdateKind: TUpdateKind): Boolean; virtual; abstract;
  public
    destructor Destroy; override;
    property DataSet: TDataSet read FDataSet;
  end;


implementation

uses SysUtils, DBConsts, LXVariants, LXAppUtils, LXDBConsts, LXDBTypes;

const
  ToUpdateKind: array [usModified..usDeleted] of TUpdateKind = (ukModify,
    ukInsert, ukDelete);

function IsDataField(Field: TField): Boolean;
begin
  Result := Field.FieldNo > 0;
end;


{ TLXDataFieldManager }

constructor TLXDataFieldManager.Create(Field: TField);
begin
  FField := Field;
end;

function TLXDataFieldManager.SetOffset(Value: Integer): Integer;
begin
  { Modified Flag  -  Buffer[Offset - 1] }
  { Null Flag      -  Buffer[Offset]     }
  { Data Buffer    -  Buffer[Offset + 1] }
  Inc(Value);
  FOffset := Value;
  Inc(Value);
  Result := Value + FField.DataSize;
end;

procedure TLXDataFieldManager.ReadData(const DataSetSupport: ILXDataSetSupport;
  RecordBuffer: PLXRecordBuffer);
var
  P: PChar;
begin
  P := PChar(RecordBuffer) + FOffset;
  P^ := Char(DataSetSupport.ReadField(FField, P + 1));
end;

function TLXDataFieldManager.DataReadBuffer(RecordBuffer: PLXRecordBuffer): PChar;
begin
  Result := PChar(RecordBuffer) + FOffset + 1;
end;

function TLXDataFieldManager.DataWriteBuffer(RecordBuffer: PLXRecordBuffer): PChar;
begin
  Result := PChar(RecordBuffer) + FOffset;
  if FField.DataSet.State <> dsNewValue then Result[-1] := #1;
  Result^ := #1;
  Inc(Result);
end;

function TLXDataFieldManager.GetData(RecordBuffer: PLXRecordBuffer; Buffer: PChar): Boolean;
var
  P: PChar;
begin
  P := PChar(RecordBuffer) + FOffset;
  Result := P^ <> #0;
  if Result and (Buffer <> nil) then
    Move(P[1], Buffer^, FField.DataSize);
end;

procedure TLXDataFieldManager.SetData(RecordBuffer: PLXRecordBuffer; Buffer: PChar);
var
  P: PChar;
begin
  P := PChar(RecordBuffer) + FOffset;
  P[-1] := #1;
  P^ := Char(Buffer <> nil);
  if Buffer <> nil then
    Move(Buffer^, P[1], FField.DataSize);
end;

function TLXDataFieldManager.GetBlobData(RecordBuffer: PLXRecordBuffer): TLXBlobData;
begin
  DatabaseErrorFmt(SInvalidBlobField, [FField.FieldName], FField.DataSet);
end;

function TLXDataFieldManager.CreateBlobStream(DataSetImpl: TLXDataSetImpl;
  RecordBuffer: PLXRecordBuffer; Mode: TBlobStreamMode): TStream;
begin
  DatabaseErrorFmt(SInvalidBlobField, [FField.FieldName], FField.DataSet);
end;

function TLXDataFieldManager.IsModified(RecordBuffer: PLXRecordBuffer): Boolean;
begin
  Result := Boolean(PChar(RecordBuffer)[FOffset - 1]);
end;

procedure TLXDataFieldManager.SetIsModified(RecordBuffer: PLXRecordBuffer; Value: Boolean);
begin
  PChar(RecordBuffer)[FOffset - 1] := Char(Value);
end;

procedure TLXDataFieldManager.Copy(Source, Dest: PLXRecordBuffer);
var
  PS, PD: PChar;
begin
  PS := PChar(Source) + FOffset;
  PD := PChar(Dest) + FOffset;
  if PS^ <> #0 then
    Move(PS[-1], PD[-1], FField.DataSize + 2) else
  begin
    PD[-1] := PS[-1];
    PD^ := #0;
  end;
end;

function TLXDataFieldManager.RequiresCleanup: Boolean;
begin
  Result := False;
end;

procedure TLXDataFieldManager.Cleanup(RecordBuffer: PLXRecordBuffer);
begin
end;


{ TLXBlobFieldManager }

function TLXBlobFieldManager.SetOffset(Value: Integer): Integer;
begin
  { Modified Flag  -  Buffer[Offset - 1] }
  { Data Buffer    -  Buffer[Offset]     }
  Inc(Value);
  FOffset := Value;
  Result := Value + SizeOf(TLXBlobData);
end;

procedure TLXBlobFieldManager.ReadData(const DataSetSupport: ILXDataSetSupport;
  RecordBuffer: PLXRecordBuffer);
begin
  DataSetSupport.ReadField(FField, PChar(RecordBuffer) + FOffset);
end;

function TLXBlobFieldManager.DataReadBuffer(RecordBuffer: PLXRecordBuffer): PChar;
begin
  Result := PChar(RecordBuffer) + FOffset;
end;

function TLXBlobFieldManager.DataWriteBuffer(RecordBuffer: PLXRecordBuffer): PChar;
begin
  Result := PChar(RecordBuffer) + FOffset;
  if FField.DataSet.State <> dsNewValue then Result[-1] := #1;
end;

function TLXBlobFieldManager.GetData(RecordBuffer: PLXRecordBuffer; Buffer: PChar): Boolean;
var
  BlobData: PLXBlobData;
begin
  BlobData := PLXBlobData(PChar(RecordBuffer) + FOffset);
  Result := BlobData^ <> '';
  if Result and (Buffer <> nil) then
    PLXBlobData(Buffer)^ := BlobData^;
end;

procedure TLXBlobFieldManager.SetData(RecordBuffer: PLXRecordBuffer; Buffer: PChar);
var
  BlobData: PLXBlobData;
begin
  BlobData := PLXBlobData(PChar(RecordBuffer) + FOffset);
  PChar(BlobData)[-1] := #1;
  if Buffer <> nil then
    BlobData^ := PLXBlobData(Buffer)^ else
    BlobData^ := '';
end;

function TLXBlobFieldManager.GetBlobData(RecordBuffer: PLXRecordBuffer): TLXBlobData;
begin
  Result := PLXBlobData(PChar(RecordBuffer) + FOffset)^;
end;

function TLXBlobFieldManager.CreateBlobStream(DataSetImpl: TLXDataSetImpl;
  RecordBuffer: PLXRecordBuffer; Mode: TBlobStreamMode): TStream;
begin
  Result := TLXBlobStream.Create(DataSetImpl, Self, RecordBuffer, Mode);
end;

procedure TLXBlobFieldManager.Copy(Source, Dest: PLXRecordBuffer);
var
  PS, PD: PChar;
begin
  PS := PChar(Source) + FOffset;
  PD := PChar(Dest) + FOffset;
  PD[-1] := PS[-1];
  PLXBlobData(PD)^ := PLXBlobData(PS)^;
end;

function TLXBlobFieldManager.RequiresCleanup: Boolean;
begin
  Result := True;
end;

procedure TLXBlobFieldManager.Cleanup(RecordBuffer: PLXRecordBuffer);
begin
  PLXBlobData(PChar(RecordBuffer) + FOffset)^ := '';
end;


{ TLXCalcFieldsManager }

constructor TLXCalcFieldsManager.Create(CalcFieldsSize: Integer);
begin
  FCalcFieldsSize := CalcFieldsSize;
end;

function TLXCalcFieldsManager.SetOffset(Value: Integer): Integer;
begin
  FCalcFieldsOffset := Value;
  Result := Value + FCalcFieldsSize;
end;

procedure TLXCalcFieldsManager.SetField(Field: TField);
begin
  FOffset := FCalcFieldsOffset + Field.Offset;
  FDataSize := Field.DataSize;
end;

function TLXCalcFieldsManager.DataReadBuffer(RecordBuffer: PLXRecordBuffer): PChar;
begin
  Result := PChar(RecordBuffer) + FOffset + 1;
end;

function TLXCalcFieldsManager.DataWriteBuffer(RecordBuffer: PLXRecordBuffer): PChar;
begin
  Result := PChar(RecordBuffer) + FOffset;
  Result^ := #1;
  Inc(Result);
end;

function TLXCalcFieldsManager.GetData(RecordBuffer: PLXRecordBuffer; Buffer: PChar): Boolean;
var
  P: PChar;
begin
  P := PChar(RecordBuffer) + FOffset;
  Result := P^ <> #0;
  if Result and (Buffer <> nil) then
    Move(P[1], Buffer^, FDataSize);
end;

procedure TLXCalcFieldsManager.SetData(RecordBuffer: PLXRecordBuffer; Buffer: PChar);
var
  P: PChar;
begin
  P := PChar(RecordBuffer) + FOffset;
  P^ := Char(Buffer <> nil);
  if Buffer <> nil then
    Move(Buffer^, P[1], FDataSize);
end;

procedure TLXCalcFieldsManager.Copy(Source, Dest: PLXRecordBuffer);
begin
  if FCalcFieldsSize <> 0 then
    Move(PChar(Source)[FCalcFieldsOffset], PChar(Dest)[FCalcFieldsOffset],
      FCalcFieldsSize);
end;

procedure TLXCalcFieldsManager.Cleanup(RecordBuffer: PLXRecordBuffer);
begin
  if FCalcFieldsSize <> 0 then
    FillChar(PChar(RecordBuffer)[FCalcFieldsOffset], FCalcFieldsSize, 0);
end;


{ TLXBlobStream }

constructor TLXBlobStream.Create(DataSetImpl: TLXDataSetImpl;
  FieldManager: TLXBlobFieldManager; RecordBuffer: PLXRecordBuffer;
  Mode: TBlobStreamMode);
begin
  FDataSetImpl := DataSetImpl;
  FFieldManager := FieldManager;
  FRecordBuffer := RecordBuffer;
  FMode := Mode;
  if Mode = bmWrite then
    FModified := True
  else if RecordBuffer <> nil then
    inherited Create(FieldManager.GetBlobData(RecordBuffer));
end;

destructor TLXBlobStream.Destroy;
begin
  if FModified and (FRecordBuffer <> nil) then
    try
      FDataString := DataString;
      FFieldManager.SetData(FRecordBuffer, @FDataString);
      FDataSetImpl.NotifyFieldChange(FFieldManager.Field);
    except
      LXApplicationHandleException(Self);
    end;
  inherited;
end;

procedure TLXBlobStream.CheckWriteMode;
begin
  if FMode = bmRead then
    DatabaseErrorFmt(SReadonlyBlobStream, [FFieldManager.Field.FieldName],
      FDataSetImpl.DataSet);
end;

procedure TLXBlobStream.SetSize(NewSize: Longint);
begin
  CheckWriteMode;
  inherited;
end;

function TLXBlobStream.Write(const Buffer; Count: Integer): Longint;
begin
  CheckWriteMode;
  Result := inherited Write(Buffer, Count);
  FModified := True;
end;


{ TLXDataSetImpl }

constructor TLXDataSetImpl.Create(DataSet: TDataSet;
  const DataSetSupport: ILXDataSetSupport; const UpdateSQLSupport: ILXUpdateSQLSupport);
begin
  inherited Create;

  FDataSet := TLXDataSetChild(DataSet);
  FDataSetSupport := DataSetSupport;
  FUpdateSQLSupport := UpdateSQLSupport;
  { Update dataset }
  FDataSet.BookmarkSize := GetBookmarkSize;
{$IFDEF D6}
  FDataSet.SetUniDirectional(LX_DEFAULT_UNIDIRECTIONAL);
{$ELSE}
  FUniDirectional := LX_DEFAULT_UNIDIRECTIONAL;
{$ENDIF}
  { Initialize self }
  FDesignActivation := LX_DEFAULT_DESIGN_ACTIVATION;
  FBufferChunks := LX_DEFAULT_BUFFER_CHUNKS;
  FPacketRecords := LX_DEFAULT_PACKET_RECORDS;
  FUpdateOptions := LX_DEFAULT_UPDATE_OPTIONS;
  FUpdateMode := LX_DEFAULT_UPDATE_MODE;
  FRefreshLocates := LX_DEFAULT_REFRESH_LOCATES;
  FRecordRefreshMode := LX_DEFAULT_RECORD_REFRESH_MODE;
  FCachedUpdates := LX_DEFAULT_CACHED_UPDATES;
  FAutoCommitUpdates := LX_DEFAULT_AUTO_COMMIT_UPDATES;
  FBOFRecord.UpdateStatus := usUnmodified;
  FEOFRecord.UpdateStatus := usUnmodified;
end;

destructor TLXDataSetImpl.Destroy;
begin
  SetUpdateObject(nil);
  FDataSetSupport := nil;
  FUpdateSQLSupport := nil;
  inherited;
end;

procedure TLXDataSetImpl.SetDesignActivation(Value: Boolean);
begin
  if Value and (csReading in FDataSet.ComponentState) and
    not (csDesigning in FDataSet.ComponentState) then FDataSet.Active := False;
  FDesignActivation := Value;
end;

{ Records Management }

procedure TLXDataSetImpl.InternalOpen;
var
  MaxFieldNo: Integer;
  I: Integer;
  Field: TField;
  FieldManager: TLXDataFieldManager;
begin
  FRecordSize := SizeOf(TLXRecordBuffer);

  FDataManagers := TList.Create;
  MaxFieldNo := 0;
  for I := 0 to FDataSet.FieldCount - 1 do
  begin
    Field := FDataSet.Fields[I];
    if IsDataField(Field) then
    begin
      FieldManager := FDataSetSupport.CreateFieldManager(Field);
      FDataManagers.Add(FieldManager);
      FRecordSize := FieldManager.SetOffset(FRecordSize);
      if FieldManager.RequiresCleanup then
      begin
        if FCleanupManagers = nil then
           FCleanupManagers := TList.Create;
        FCleanupManagers.Add(FieldManager);
      end;
      if Field.FieldNo > MaxFieldNo then
        MaxFieldNo := Field.FieldNo;
    end;
  end;
  if MaxFieldNo > 0 then
  begin
    SetLength(FFieldNoToManager, MaxFieldNo + 1);
    for I := 0 to FDataManagers.Count - 1 do
    begin
      FieldManager := FDataManagers[I];
      FFieldNoToManager[FieldManager.Field.FieldNo] := FieldManager;
    end;
  end;

  if FDataSetSupport.CustomDataSize = 0 then
    FCustomDataOffset := 0 else
  begin
    FCustomDataOffset := FRecordSize;
    Inc(FRecordSize, FDataSetSupport.CustomDataSize);
  end;

  FCalcsManager := TLXCalcFieldsManager.Create(FDataSet.CalcFieldsSize);
  FRecordSize := FCalcsManager.SetOffset(FRecordSize);

  if UniDirectional then
    FRecords := TLXRecordAllocator.Create(FRecordSize, 1) else
    FRecords := TLXRecordAllocator.Create(FRecordSize, FBufferChunks);
  if FCleanupManagers <> nil then
    FRecords.OnFreeRecord := ClearRecord;

  FBOFRecord.Next := nil;
  FEOFRecord.Prior := @FBOFRecord;
  FCurrentRecord := @FBOFRecord;
end;

procedure TLXDataSetImpl.InternalClose;
var
  I: Integer;
begin
  FreeAndNil(FUpdatedRecords);
  FreeAndNil(FAppliedRecords);
  FreeAndNil(FRecords);
  FreeAndNil(FCalcsManager);
  FFieldNoToManager := nil;
  FreeAndNil(FCleanupManagers);
  if FDataManagers <> nil then
  begin
    for I := 0 to FDataManagers.Count - 1 do
      TLXDataFieldManager(FDataManagers[I]).Free;
    FreeAndNil(FDataManagers);
  end;
end;

function TLXDataSetImpl.IsCursorOpen: Boolean;
begin
  Result := FRecords <> nil;
end;

function TLXDataSetImpl.GetRecord(GetMode: TGetMode): TGetResult;

  function GetNextRecord: TGetResult;
  begin
    repeat
      if FCurrentRecord = @FEOFRecord then
        Result := grEOF
      else if FCurrentRecord^.Next = nil then
        Result := FetchNextRecord
      else
      begin
        FCurrentRecord := FCurrentRecord^.Next;
        if FCurrentRecord <> @FEOFRecord then
          Result := grOK else
          Result := grEOF;
      end;
    until IsInStatusFilter(FCurrentRecord^.UpdateStatus) or (Result <> grOK);
  end;

  function GetPriorRecord: TGetResult;
  begin
    if FCurrentRecord = @FBOFRecord then
      Result := grBOF else
    begin
      Result := grOK;
      repeat
        FCurrentRecord := FCurrentRecord^.Prior;
        if FCurrentRecord = @FBOFRecord then
        begin
          Result := grBOF;
          Break;
        end;
      until IsInStatusFilter(FCurrentRecord^.UpdateStatus);
    end;
  end;

  function GetCurrentRecord: TGetResult;
  begin
    if FCurrentRecord = @FBOFRecord then
      Result := grBOF
    else if FCurrentRecord = @FEOFRecord then
      Result := grEOF
    else if IsInStatusFilter(FCurrentRecord^.UpdateStatus) then
      Result := grOK
    else
      Result := grError;
  end;

  function InternalGetRecord(GetMode: TGetMode): TGetResult;
  begin
    case GetMode of
      gmNext: Result := GetNextRecord;
      gmPrior: Result := GetPriorRecord;
      gmCurrent: Result := GetCurrentRecord;
      else Result := grError;
    end;
  end;

var
  Accept: Boolean;
  SaveState: TDataSetState;
begin
  if not IsFiltered then
    Result := InternalGetRecord(GetMode) else
  begin
    SaveState := FDataSet.SetTempState(dsFilter);
    try
      repeat
        Result := InternalGetRecord(GetMode);
        if Result = grOK then
        begin
          FFilterBuffer := FCurrentRecord;
          Accept := True;
          FDataSet.OnFilterRecord(FDataSet, Accept);
          if not Accept and (GetMode = gmCurrent) then
            Result := grError;
        end;
      until Accept or (Result <> grOK);
    except
      Result := grError;
    end;
    FDataSet.RestoreState(SaveState);
  end;
end;

function TLXDataSetImpl.GetRecord(Buffer: TRecordBuffer; GetMode: TGetMode;
  DoCheck: Boolean): TGetResult;
begin
  Result := GetRecord(GetMode);
  if Result <> grOK then
    PPRecordBuffer(Buffer)^ := nil else
  begin
    PPRecordBuffer(Buffer)^ := FCurrentRecord;
    if FDataSet.AutoCalcFields then
      FDataSet.GetCalcFields(@FCurrentRecord);
  end;
end;

procedure TLXDataSetImpl.FetchAll;
begin
  InternalLast;
  FDataSet.CursorPosChanged;
end;

procedure TLXDataSetImpl.Requery;
begin
  FDataSet.CheckBrowseMode;
  FDataSet.DoBeforeRefresh;
  try
    InternalRestart;
  finally
    if GetRecord(TRecordBuffer(FDataSet.Buffers[0]), gmNext, False) <> grOK then
    begin
      FDataSet.ClearBuffers;
      FDataSet.DataEvent(deDataSetChange, 0);
    end else
    begin
      FDataSet.ActivateBuffers;
      try
        FDataSet.GetNextRecords;
      finally
        FDataSet.DataEvent(deDataSetChange, 0);
      end;
    end;
    FDataSet.DoAfterRefresh;
  end;
end;

procedure TLXDataSetImpl.SetBufferChunks(Value: Integer);
begin
  FDataSet.CheckInactive;
  FBufferChunks := Value;
end;

procedure TLXDataSetImpl.SetPacketRecords(Value: Integer);
begin
  if Value = 0 then Value := LX_DEFAULT_PACKET_RECORDS;
  if (Value <> FPacketRecords) and (FPacketRecords = -1)
    and not UniDirectional and IsCursorOpen then FetchAll;
  FPacketRecords := Value;
end;

function TLXDataSetImpl.InternalFetchNextRecord: TGetResult;

  procedure HandleUniDirectional;
  begin
    if FCurrentRecord <> @FBOFRecord then
    begin
      if FCleanupManagers <> nil then
        ClearRecord(FCurrentRecord);
    end else
    begin
      FCurrentRecord := AllocRecord;
      FCurrentRecord^.Next := nil;
      FCurrentRecord^.Prior := @FBOFRecord;
    end;
    FCurrentRecord^.UpdateStatus := usUnmodified;
    Inc(FCurrentRecord^.SequenceNumber);
  end;

  procedure HandleBiDirectional;
  var
    FetchedRecord: PLXRecordBuffer;
  begin
    FetchedRecord := AllocRecord;
    FetchedRecord^.Next := nil;
    FetchedRecord^.Prior := FCurrentRecord;
    FetchedRecord^.UpdateStatus := usUnmodified;
    FetchedRecord^.SequenceNumber := FCurrentRecord^.SequenceNumber + 1;
    FCurrentRecord^.Next := FetchedRecord;
    FEOFRecord.Prior := FetchedRecord;
    FCurrentRecord := FetchedRecord;
  end;

begin
  Result := FDataSetSupport.FetchNext;
  if Result = grOK then
  begin
    if UniDirectional then
      HandleUniDirectional else
      HandleBiDirectional;
    FillRecordFromCursor(FCurrentRecord);
  end else
  begin
    FCurrentRecord^.Next := @FEOFRecord;
    FEOFRecord.Prior := FCurrentRecord;
    FCurrentRecord := @FEOFRecord;
  end;
end;

function TLXDataSetImpl.FetchNextRecord: TGetResult;
var
  CurrentRecord: PLXRecordBuffer;
  I: Integer;
begin
  Result := InternalFetchNextRecord;
  if not UniDirectional and (Result = grOK) and (FPacketRecords <> 1) then
  begin
    CurrentRecord := FCurrentRecord;
    try
      if FPacketRecords < 0 then
        repeat until InternalFetchNextRecord <> grOK else
      begin
        for I := 2 to FPacketRecords do
          if InternalFetchNextRecord <> grOK then Break;
      end;
    finally
      FCurrentRecord := CurrentRecord;
    end;
  end;
end;

procedure TLXDataSetImpl.FillRecordFromCursor(RecordBuffer: PLXRecordBuffer);
var
  I: Integer;
begin
  for I := 0 to FDataManagers.Count - 1 do
    TLXDataFieldManager(FDataManagers[I]).ReadData(FDataSetSupport, RecordBuffer);
  if FCustomDataOffset > 0 then
    FDataSetSupport.ReadCustomData(PChar(RecordBuffer) + FCustomDataOffset);
  if not FDataSet.AutoCalcFields then
    FDataSet.GetCalcFields(@RecordBuffer);
end;

{ Buffer Management }

function TLXDataSetImpl.AllocRecord: PLXRecordBuffer;
begin
  Result := FRecords.AllocRecord;
end;

procedure TLXDataSetImpl.ClearRecord(RecordBuffer: Pointer);
var
  I: Integer;
begin
  for I := 0 to FCleanupManagers.Count - 1 do
    TLXDataFieldManager(FCleanupManagers[I]).Cleanup(RecordBuffer);
end;

procedure TLXDataSetImpl.FreeRecord(RecordBuffer: PLXRecordBuffer);
begin
  if RecordBuffer^.Backup <> nil then
    FreeRecord(RecordBuffer^.Backup);
  FRecords.FreeRecord(RecordBuffer);
end;

procedure TLXDataSetImpl.InsertRecord(RecordBuffer: PLXRecordBuffer);
var
  PriorRecord: PLXRecordBuffer;
  NextRecord: PLXRecordBuffer;
begin
  PriorRecord := RecordBuffer^.Prior;
  NextRecord := RecordBuffer^.Next;
  PriorRecord^.Next := RecordBuffer;
  NextRecord^.Prior := RecordBuffer;
  while NextRecord <> nil do
  begin
    Inc(NextRecord^.SequenceNumber);
    NextRecord := NextRecord^.Next;
  end;
end;

procedure TLXDataSetImpl.DeleteRecord(RecordBuffer: PLXRecordBuffer);
var
  PriorRecord: PLXRecordBuffer;
  NextRecord: PLXRecordBuffer;
begin
  PriorRecord := RecordBuffer^.Prior;
  NextRecord := RecordBuffer^.Next;
  PriorRecord^.Next := NextRecord;
  NextRecord^.Prior := PriorRecord;
  FreeRecord(RecordBuffer);
  while NextRecord <> nil do
  begin
    Dec(NextRecord^.SequenceNumber);
    NextRecord := NextRecord^.Next;
  end;
end;

procedure TLXDataSetImpl.CopyRecord(Source, Dest: PLXRecordBuffer);
var
  I: Integer;
begin
  for I := 0 to FDataManagers.Count - 1 do
    TLXDataFieldManager(FDataManagers[I]).Copy(Source, Dest);
  FCalcsManager.Copy(Source, Dest);
end;

function TLXDataSetImpl.AllocRecordBuffer: TRecordBuffer;
begin
  GetMem(Result, SizeOf(PLXRecordBuffer));
end;

procedure TLXDataSetImpl.FreeRecordBuffer(var Buffer: TRecordBuffer);
begin
  FreeMem(Buffer);
end;

procedure TLXDataSetImpl.InitRecord(Buffer: TRecordBuffer);
var
  RecordBuffer: PLXRecordBuffer;
begin
  RecordBuffer := AllocRecord;
  RecordBuffer^.UpdateStatus := usInserted;
  RecordBuffer^.SequenceNumber := -1;
  PPRecordBuffer(Buffer)^ := RecordBuffer;
end;

procedure TLXDataSetImpl.ClearCalcFields(Buffer: TRecordBuffer);
begin
  FCalcsManager.Cleanup(PPRecordBuffer(Buffer)^);
end;

function TLXDataSetImpl.GetCurrentRecord(Buffer: TRecordBuffer): Boolean;
var
  RecordBuffer: PLXRecordBuffer;
begin
  RecordBuffer := GetActiveRecordBuffer;
  Result := RecordBuffer <> nil;
  if Result and (Buffer <> nil) then
    PPRecordBuffer(Buffer)^ := RecordBuffer;
end;

function TLXDataSetImpl.SetDataSetState(DataSet: TDataSet; State: TDataSetState): TLXDataSetStateContext;
begin
  Result.State := TLXDataSetChild(DataSet).SetTempState(State);
  Result.Modified := DataSet.Modified;
end;

procedure TLXDataSetImpl.RestoreDataSetState(DataSet: TDataSet;
  Context: TLXDataSetStateContext);
begin
  Context.Modified := Context.Modified or DataSet.Modified;
  TLXDataSetChild(DataSet).RestoreState(Context.State);
  TLXDataSetChild(DataSet).SetModified(Context.Modified);
end;

{ Field Management }

function TLXDataSetImpl.GetActiveRecordBuffer: PLXRecordBuffer;
begin
  if not FDataSet.IsEmpty then
    Result := PPRecordBuffer(FDataSet.ActiveBuffer)^ else
    Result := nil;
end;

function TLXDataSetImpl.GetFieldsRecordBuffer(State: TDataSetState): PLXRecordBuffer;

  function GetOldBuffer(Buffer: PLXRecordBuffer): PLXRecordBuffer;
  begin
    Result := Buffer;
    if (Result <> nil) and (Buffer^.Backup <> nil) then
      Result := Buffer^.Backup;
  end;

begin
  case State of
    dsBrowse, dsBlockRead:
      Result := GetActiveRecordBuffer;
    dsEdit, dsInsert:
      Result := PPRecordBuffer(FDataSet.ActiveBuffer)^;
    dsCalcFields:
      Result := PPRecordBuffer(FDataSet.CalcBuffer)^;
    dsFilter:
      Result := FFilterBuffer;
    dsNewValue:
      if FUpdatedBuffer <> nil then
        Result := FUpdatedBuffer else
        Result := GetActiveRecordBuffer;
    dsOldValue:
      if FUpdatedBuffer <> nil then
        Result := GetOldBuffer(FUpdatedBuffer) else
        Result := GetOldBuffer(GetActiveRecordBuffer);
    else
      Result := nil;
  end;
end;

function TLXDataSetImpl.GetFieldsRecordBuffer(var RecordBuffer: PLXRecordBuffer;
  State: TDataSetState): Boolean;
begin
  RecordBuffer := GetFieldsRecordBuffer(State);
  Result := RecordBuffer <> nil;
end;

procedure TLXDataSetImpl.CheckWriteModes(Field: TField);
begin
  if IsDataField(Field) then
  begin
    if Field.ReadOnly then
    begin
      if FDataSet.State <> dsNewValue then
        DatabaseErrorFmt(SFieldReadOnly, [Field.DisplayName], FDataSet);
    end else
      if not (FDataSet.State in [dsEdit, dsInsert, dsNewValue]) then
        DatabaseError(SNotEditing, FDataSet);
  end else
    if not (FDataSet.State in [dsEdit, dsInsert, dsCalcFields, dsNewValue]) then
      DatabaseError(SNotEditing, FDataSet);
end;

function TLXDataSetImpl.GetFieldManager(Field: TField): TLXFieldManager;
begin
  if IsDataField(Field) then
    Result := FFieldNoToManager[Field.FieldNo] else
  begin
    FCalcsManager.SetField(Field);
    Result := FCalcsManager;
  end;
end;

function TLXDataSetImpl.FieldDataReadBuffer(Field: TField): PChar;
var
  RecordBuffer: PLXRecordBuffer;
begin
  if GetFieldsRecordBuffer(RecordBuffer, FDataSet.State) then
    Result := GetFieldManager(Field).DataReadBuffer(RecordBuffer) else
    Result := nil;
end;

function TLXDataSetImpl.FieldDataWriteBuffer(Field: TField): PChar;
var
  RecordBuffer: PLXRecordBuffer;
begin
  CheckWriteModes(Field);
  if GetFieldsRecordBuffer(RecordBuffer, FDataSet.State) then
    Result := GetFieldManager(Field).DataWriteBuffer(RecordBuffer) else
    Result := nil;
end;

procedure TLXDataSetImpl.NotifyFieldChange(Field: TField);
begin
  if not (FDataSet.State in [dsCalcFields, dsNewValue]) then
    FDataSet.DataEvent(deFieldChange, Longint(Field));
end;

function TLXDataSetImpl.GetFieldData(Field: TField; Buffer: Pointer): Boolean;
var
  RecordBuffer: PLXRecordBuffer;
begin
  Result := GetFieldsRecordBuffer(RecordBuffer, FDataSet.State);
  if Result then
    Result := GetFieldManager(Field).GetData(RecordBuffer, Buffer);
end;

function TLXDataSetImpl.GetFieldData(FieldNo: Integer; Buffer: Pointer): Boolean;
var
  RecordBuffer: PLXRecordBuffer;
begin
  Result := GetFieldsRecordBuffer(RecordBuffer, FDataSet.State);
  if Result then                    
    Result := FFieldNoToManager[FieldNo].GetData(RecordBuffer, Buffer);
end;

function TLXDataSetImpl.GetBlobFieldData(FieldNo: Integer; var Buffer: TBlobByteData): Integer;
var
  RecordBuffer: PLXRecordBuffer;
  BlobData: TLXBlobData;
begin
  if not GetFieldsRecordBuffer(RecordBuffer, FDataSet.State) then
  begin
    Buffer := nil;
    Result := 0;
  end else
  begin
    BlobData := FFieldNoToManager[FieldNo].GetBlobData(RecordBuffer);
    Result := Length(BlobData);
    SetLength(Buffer, Result);
    Move(PChar(BlobData)^, Pointer(Buffer)^, Result);
  end;
end;

function TLXDataSetImpl.HasFieldStateValue(State: TDataSetState; Field: TField): Boolean;
var
  RecordBuffer: PLXRecordBuffer;
begin
  if State <> dsNewValue then
    Result := True else
  begin
    Result := GetFieldsRecordBuffer(RecordBuffer, State);
    if Result then
      Result := FFieldNoToManager[Field.FieldNo].IsModified(RecordBuffer);
  end;
end;

procedure TLXDataSetImpl.SetFieldData(Field: TField; Buffer: Pointer);
var
  RecordBuffer: PLXRecordBuffer;
begin
  CheckWriteModes(Field);
  if GetFieldsRecordBuffer(RecordBuffer, FDataSet.State) then
  begin
    Field.Validate(Buffer);
    GetFieldManager(Field).SetData(RecordBuffer, Buffer);
    NotifyFieldChange(Field);
  end;
end;

function TLXDataSetImpl.CreateBlobStream(Field: TField;
  Mode: TBlobStreamMode): TStream;
begin
  if Mode <> bmRead then
    CheckWriteModes(Field);
  Result := FFieldNoToManager[Field.FieldNo].CreateBlobStream(Self,
    GetFieldsRecordBuffer(FDataSet.State), Mode);
end;

function TLXDataSetImpl.CustomDataBuffer: PChar;
begin
  if GetFieldsRecordBuffer(PLXRecordBuffer(Result), FDataSet.State) then
    Inc(Result, FCustomDataOffset);
end;

{ Navigation }

function TLXDataSetImpl.MoveFromCurrentRecord: PLXRecordBuffer;
begin
  Result := FCurrentRecord;
  if GetRecord(gmNext) = grEOF then
    FCurrentRecord := Result^.Prior;
end;

procedure TLXDataSetImpl.SkipRecords(Value: Integer);
begin
  while (Value > 0) and (GetRecord(gmNext) = grOK) do
    Dec(Value);
end;

procedure TLXDataSetImpl.InternalFirst;
begin
  FCurrentRecord := @FBOFRecord;
end;

procedure TLXDataSetImpl.InternalLast;
begin
  if not IsAllFetched then
  begin
    FCurrentRecord := FEOFRecord.Prior;
    repeat until InternalFetchNextRecord <> grOK;
  end;
  FCurrentRecord := @FEOFRecord;
end;

{ Editing }

procedure TLXDataSetImpl.InternalInitRecord(Buffer: TRecordBuffer);
var
  RecordBuffer: PLXRecordBuffer;
  I: Integer;
begin
  { in this implementation called only from DataSet.ClearFields }
  RecordBuffer := PPRecordBuffer(Buffer)^;
  for I := 0 to FDataManagers.Count - 1 do
    TLXDataFieldManager(FDataManagers[I]).SetData(RecordBuffer, nil);
  FCalcsManager.Cleanup(RecordBuffer);
end;

procedure TLXDataSetImpl.InternalAddRecord(Buffer: Pointer; Append: Boolean);
var
  InsertedRecord: PLXRecordBuffer;
begin
  if Append then InternalLast;
  InsertedRecord := PPRecordBuffer(Buffer)^;
  InsertedRecord^.Next := FCurrentRecord;
  InsertedRecord^.Prior := FCurrentRecord^.Prior;
  InsertedRecord^.SequenceNumber := FCurrentRecord^.SequenceNumber;;
  FCurrentRecord := InsertedRecord;
  InternalPost;
end;

procedure TLXDataSetImpl.InternalInsert;
begin
  FDataSet.CursorPosChanged;
end;

procedure TLXDataSetImpl.InternalEdit;
begin
  if FCurrentRecord^.UpdateStatus = usDeleted then
    DatabaseError(SDeletedRecordEdit, FDataSet);
  FRecordBackup := AllocRecord;
  CopyRecord(FCurrentRecord, FRecordBackup);
end;

procedure TLXDataSetImpl.InternalDelete;
begin
  case FCurrentRecord^.UpdateStatus of
    usUnmodified:
      RegisterUpdate(usDeleted);
    usModified:
      FCurrentRecord^.UpdateStatus := usDeleted;
    usInserted:
      begin
        UnregisterUpdate;
        DeleteRecord(MoveFromCurrentRecord);
      end;
    usDeleted:
      DatabaseError(SDeletedRecordDelete, FDataSet);
  end;
end;

procedure TLXDataSetImpl.InternalPost;
begin
  case FDataSet.State of
    dsInsert:
      begin
        RegisterUpdate(usInserted);
        InsertRecord(FCurrentRecord);
      end;
    dsEdit:
      begin
        if FDataSet.Modified and (FCurrentRecord^.UpdateStatus = usUnmodified) then
          RegisterUpdate(usModified) else
          FreeRecord(FRecordBackup);
        FRecordBackup := nil;
      end;
  end;
end;

procedure TLXDataSetImpl.InternalCancel;
begin
  case FDataSet.State of
    dsInsert:
      FreeRecord(MoveFromCurrentRecord);
    dsEdit:
      begin
        CopyRecord(FRecordBackup, FCurrentRecord);
        FreeRecord(FRecordBackup);
        FRecordBackup := nil;
      end;
  end;
end;

procedure TLXDataSetImpl.InternalRestart;
begin
  InternalClose;
  FDataSetSupport.Restart;
  InternalOpen;
end;

procedure TLXDataSetImpl.InternalRefresh;
var
  KeyFields: string;
  KeyValues: Variant;
  RecNo: Integer;
begin
  if FRefreshLocates then
    KeyFields := IProviderSupport(FDataSet).PSGetKeyFields;
  if KeyFields <> '' then
  begin
    KeyValues := FDataSet.FieldValues[KeyFields];
    InternalRestart;
    LocateRecord(KeyFields, KeyValues, [], True);
  end else
  begin
    RecNo := GetRecNo;
    InternalRestart;
    SkipRecords(RecNo);
  end;
end;

procedure TLXDataSetImpl.InternalEmpty;
begin
  FDataSet.ClearBuffers;
  InternalClose;
  InternalOpen;
  FBOFRecord.Next := @FEOFRecord;
  FDataSet.DataEvent(deDataSetChange, 0);
end;

function TLXDataSetImpl.UpdateRecord(UpdateKind: TUpdateKind;
  Delta: TDataSet): Boolean;
var
  UpdateAction: TUpdateAction;
begin
  repeat
    UpdateAction := uaFail;
    try
      if Assigned(FOnUpdateRecord) then
        FOnUpdateRecord(Delta, UpdateKind, UpdateAction);
      if UpdateAction = uaFail then
        if DoModifyRecord(Delta, UpdateKind) then
          UpdateAction := uaApplied else
          UpdateAction := uaSkip;
    except
      on E: Exception do
      begin
        UpdateAction := uaFail;
        if E is EDatabaseError then
          DoUpdateError(EDatabaseError(E), UpdateKind, UpdateAction);
        if UpdateAction = uaFail then
          raise;
      end;
    end;
  until UpdateAction <> uaRetry;

  case UpdateAction of
    uaAbort: Abort;
    uaApplied: DoUpdateApplied(Delta, UpdateKind);
    uaFail: DatabaseError(SUpdateFailed, Delta);
  end;

  Result := UpdateAction = uaApplied;
end;

procedure TLXDataSetImpl.ApplyUpdates;
begin
  ApplyUpdates(AutoCommitUpdates);
end;

procedure TLXDataSetImpl.ApplyUpdates(CommitUpdates: Boolean);
var
  LocalTransaction: Boolean;
  I: Integer;
begin
  FDataSet.CheckBrowseMode;
  if FUpdatedRecords = nil then Exit;

  FDataSet.DisableControls;
  try
    if Assigned(FBeforeApplyUpdates) then
      FBeforeApplyUpdates(FDataSet);

    FDataSet.DoBeforeScroll;

    FreeAndNil(FAppliedRecords);
    FAppliedRecords := TList.Create;
    LocalTransaction := (uoUpdateInTransaction in UpdateOptions) and
      not FDataSet.PSInTransaction;
    if LocalTransaction then
      FDataSet.PSStartTransaction;
    try
      for I := 0 to FUpdatedRecords.Count - 1 do
      begin
        FUpdatedBuffer := FUpdatedRecords[I];
        if UpdateRecord(ToUpdateKind[FUpdatedBuffer^.UpdateStatus], FDataSet) then
          FAppliedRecords.Add(FUpdatedBuffer);
      end;
      FUpdatedBuffer := nil;
      if LocalTransaction then
        FDataSet.PSEndTransaction(True);
    except
      FUpdatedBuffer := nil;
      if LocalTransaction then
        FDataSet.PSEndTransaction(False);
      raise;
    end;

    if Assigned(FAfterApplyUpdates) then
      FAfterApplyUpdates(FDataSet);

    if CommitUpdates then
      Self.CommitUpdates;
  finally
    FDataSet.EnableControls;
  end;
end;

procedure TLXDataSetImpl.CommitUpdates;
var
  I: Integer;
begin
  FDataSet.CheckBrowseMode;
  if FAppliedRecords = nil then Exit;
  FDataSet.DoBeforeScroll;
  FDataSet.UpdateCursorPos;
  for I := 0 to FAppliedRecords.Count - 1 do
  begin
    CommitUpdate(FAppliedRecords[I]);
    FUpdatedRecords.Remove(FAppliedRecords[I]);
  end;
  if FUpdatedRecords.Count = 0 then
    FreeAndNil(FUpdatedRecords);
  FreeAndNil(FAppliedRecords);
  ResyncAndCheckForScroll;
end;

procedure TLXDataSetImpl.CancelUpdates;
var
  I: Integer;
begin
  FDataSet.Cancel;
  if not UpdatesPending then Exit;
  FDataSet.DoBeforeScroll;
  FDataSet.UpdateCursorPos;
  for I := FUpdatedRecords.Count - 1 downto 0 do
    RollbackUpdate(FUpdatedRecords[I]);
  FreeAndNil(FUpdatedRecords);
  FreeAndNil(FAppliedRecords);
  ResyncAndCheckForScroll;
end;

procedure TLXDataSetImpl.RevertRecord;
begin
  FDataSet.Cancel;
  if not UpdatesPending then Exit;
  FDataSet.DoBeforeScroll;
  FDataSet.UpdateCursorPos;
  if UnregisterUpdate then
  begin
    RollbackUpdate(FCurrentRecord);
    ResyncAndCheckForScroll;
  end;
end;

function TLXDataSetImpl.GetTableName: string;
begin
  Result := FDataSet.PSGetTableName;
  if Assigned(FOnGetTableName) then
    FOnGetTableName(FDataSet, Result);
end;

procedure TLXDataSetImpl.SetUpdateObject(Value: TLXDataSetUpdateObject);
begin
  if Value <> FUpdateObject then
  begin
    if FUpdateObject <> nil then
      FUpdateObject.SetDataSetImpl(nil);
    FUpdateObject := Value;
    if FUpdateObject <> nil then
      FUpdateObject.SetDataSetImpl(Self);
  end;
end;

procedure TLXDataSetImpl.SetCachedUpdates(Value: Boolean);
begin
  if Value <> FCachedUpdates then
    if (FDataSet.State = dsInActive) or (csDesigning in FDataSet.ComponentState) then
      FCachedUpdates := Value else
    begin
      if not Value then
        CancelUpdates else
      begin
        FDataSet.CheckBrowseMode;
        FDataSet.UpdateCursorPos;
      end;
      FDataSet.UpdateCursorPos;
      FCachedUpdates := Value;
      FDataSet.FreeFieldBuffers;
      FDataSet.SetBufListSize(0);
      try
        FDataSet.SetBufListSize(FDataSet.BufferCount + 1);
      except
        FDataSet.SetState(dsInactive);
        FDataSet.CloseCursor;
        raise;
      end;
      FDataSet.Resync([]);
    end;
end;

function TLXDataSetImpl.GetUpdatedRecords: TList;
begin
  if FUpdatedRecords = nil then
    FUpdatedRecords := TList.Create;
  Result := FUpdatedRecords;
end;

function TLXDataSetImpl.DoModifyRecord(Delta: TDataSet; UpdateKind: TUpdateKind): Boolean;
var
  RowsAffected: Integer;
  SQLGenerator: TLXSQLGenerator;
begin
  Result := (FUpdateObject <> nil) and FUpdateObject.Apply(Delta, UpdateKind,
    RowsAffected);

  if not Result and (uoAllowAutomaticUpdate in UpdateOptions) then
  begin
    SQLGenerator := FUpdateSQLSupport.CreateSQLGenerator(Delta);
    try
      RowsAffected := FUpdateSQLSupport.ModifyRecord(Delta, UpdateKind,
        SQLGenerator.GenerateModifySQL(UpdateKind));
    finally
      SQLGenerator.Free;
    end;
    Result := True;
  end;

  if Result then
    if uoCheckRowsAffected in UpdateOptions then
      if RowsAffected > 1 then
      begin
        if not (uoAllowMultiRecordUpdates in UpdateOptions) then
          DatabaseError(STooManyRecordsModified, FDataSet);
      end else
        if RowsAffected = 0 then
          DatabaseError(SRecordChanged, FDataSet);
end;

procedure TLXDataSetImpl.DoUpdateApplied(Delta: TDataSet; UpdateKind: TUpdateKind);
var
  RefreshMode: TLXRecordRefreshMode;
begin
  RefreshMode := FRecordRefreshMode;

  if Assigned(FOnUpdateApplied) then
    FOnUpdateApplied(Delta, UpdateKind, RefreshMode);

  if ((UpdateKind = ukInsert) and (RefreshMode <> rmNone)) or
    ((UpdateKind = ukModify) and (RefreshMode = rmPosted)) then
  begin
    if ((FUpdateObject = nil) or not FUpdateObject.Refresh(Delta, UpdateKind)) and
      (uoAllowAutomaticUpdate in UpdateOptions) then
    begin
      with FUpdateSQLSupport.CreateSQLGenerator(Delta) do
        try
          if (UpdateKind = ukInsert) and (UpdateMode = upWhereAll) then
            UpdateMode := upWhereChanged;
          FUpdateSQLSupport.RefreshRecord(Delta, UpdateKind, GenerateRefreshSQL);
        finally
          Free;
        end;
    end;
  end;
end;

procedure TLXDataSetImpl.DoUpdateError(E: EDatabaseError;
  UpdateKind: TUpdateKind; var UpdateAction: TUpdateAction);
begin
  if Assigned(FOnUpdateError) then
    FOnUpdateError(FDataSet, E, UpdateKind, UpdateAction);
end;

procedure TLXDataSetImpl.RegisterUpdate(UpdateStatus: TUpdateStatus);
var
  LocalTransaction: Boolean;
begin
  FCurrentRecord^.UpdateStatus := UpdateStatus;
  FCurrentRecord^.Backup := FRecordBackup;
  if FCachedUpdates then
    GetUpdatedRecords.Add(FCurrentRecord) else
  begin
    FUpdatedBuffer := FCurrentRecord;
    try
      if Assigned(FBeforeApplyUpdates) then
        FBeforeApplyUpdates(FDataSet);
      LocalTransaction := (uoUpdateInTransaction in UpdateOptions) and
        not FDataSet.PSInTransaction;
      if LocalTransaction then
        FDataSet.PSStartTransaction;
      try
        if UpdateRecord(ToUpdateKind[UpdateStatus], FDataSet) then
          CommitUpdate(FUpdatedBuffer) else
          DatabaseError(SCannotSkipUpdate, FDataSet);
        if LocalTransaction then
          FDataSet.PSEndTransaction(True);
      except
        if LocalTransaction then
          FDataSet.PSEndTransaction(False);
        raise;
      end;
      if Assigned(FAfterApplyUpdates) then
        FAfterApplyUpdates(FDataSet);
      FUpdatedBuffer := nil;
    except
      if UpdateStatus <> usInserted then
        FUpdatedBuffer^.UpdateStatus := usUnmodified;
      FUpdatedBuffer^.Backup := nil;
      FUpdatedBuffer := nil;
      raise;
    end;
  end;
end;

function TLXDataSetImpl.UnregisterUpdate: Boolean;
begin
  Result := FUpdatedRecords.Remove(FCurrentRecord) >= 0;
  if Result then
    if FUpdatedRecords.Count = 0 then
    begin
      FreeAndNil(FUpdatedRecords);
      FreeAndNil(FAppliedRecords);
    end else
      if FAppliedRecords <> nil then
      begin
        FAppliedRecords.Remove(FCurrentRecord);
        if FAppliedRecords.Count = 0 then
          FreeAndNil(FAppliedRecords);
      end;
end;

procedure TLXDataSetImpl.CommitUpdate(RecordBuffer: PLXRecordBuffer);
var
  I: Integer;
begin
  if RecordBuffer^.UpdateStatus = usDeleted then
  begin
    if RecordBuffer = FCurrentRecord then
      MoveFromCurrentRecord;
    DeleteRecord(RecordBuffer);
  end else
  begin
    if RecordBuffer^.UpdateStatus = usModified then
    begin
      FreeRecord(RecordBuffer^.Backup);
      RecordBuffer^.Backup := nil;
    end;
    RecordBuffer^.UpdateStatus := usUnmodified;
    for I := 0 to FDataManagers.Count - 1 do
      TLXDataFieldManager(FDataManagers[I]).SetIsModified(RecordBuffer, False);
    if (RecordBuffer = FCurrentRecord) and not IsInStatusFilter(usUnmodified) then
      MoveFromCurrentRecord;
  end;
end;

procedure TLXDataSetImpl.RollbackUpdate(RecordBuffer: PLXRecordBuffer);
begin
  case RecordBuffer^.UpdateStatus of
    usInserted:
      begin
        if RecordBuffer = FCurrentRecord then
          MoveFromCurrentRecord;
        DeleteRecord(RecordBuffer);
      end;
    usModified, usDeleted:
      begin
        if (RecordBuffer = FCurrentRecord) and not IsInStatusFilter(usUnmodified) then
          MoveFromCurrentRecord;
        if RecordBuffer^.Backup <> nil then
        begin
          CopyRecord(RecordBuffer^.Backup, RecordBuffer);
          FreeRecord(RecordBuffer^.Backup);
          RecordBuffer^.Backup := nil;
        end;
        RecordBuffer^.UpdateStatus := usUnmodified;
      end;
  end;
end;

procedure TLXDataSetImpl.ResyncAndCheckForScroll;
begin
  FDataSet.Resync([]);
  if FCurrentRecord <> GetActiveRecordBuffer then
    FDataSet.DoAfterScroll;
end;

{ Informational }

function TLXDataSetImpl.GetUniDirectional: Boolean;
begin
{$IFDEF D6}
  Result := FDataSet.IsUniDirectional;
{$ELSE}
  Result := FUniDirectional;
{$ENDIF}
end;

procedure TLXDataSetImpl.SetUniDirectional(Value: Boolean);
begin
  FDataSet.CheckInactive;
{$IFDEF D6}
  FDataSet.SetUniDirectional(Value);
{$ELSE}
  FUniDirectional := Value;
{$ENDIF}
end;

function TLXDataSetImpl.GetUpdatesPending: Boolean;
begin
  Result := FUpdatedRecords <> nil;
  if Result and (FAppliedRecords <> nil) then
    Result := FUpdatedRecords.Count - FAppliedRecords.Count > 0;
end;

function TLXDataSetImpl.IsAllFetched: Boolean;
begin
  Result := FEOFRecord.Prior^.Next <> nil;
end;

function TLXDataSetImpl.IsFiltered: Boolean;
begin
  Result := FDataSet.Filtered and Assigned(FDataSet.OnFilterRecord);
end;

function TLXDataSetImpl.GetCanModify: Boolean;
begin
  Result := not UniDirectional and ((FUpdateObject <> nil) or FCachedUpdates or
    ((uoAllowAutomaticUpdate in UpdateOptions) and (GetTableName <> '')));
end;

function TLXDataSetImpl.GetRecordCount: Longint;
begin
  FDataSet.CheckActive;
  if not IsFiltered then
  begin
    FetchAll;
    Result := FEOFRecord.Prior^.SequenceNumber;
  end else
  begin
    Result := 0;
    InternalFirst;
    while GetRecord(gmNext) = grOK do Inc(Result);
    FDataSet.CursorPosChanged;
  end;
end;

procedure TLXDataSetImpl.SetRecNo(Value: Integer);
begin
  FDataSet.CheckBrowseMode;
  if Value <> GetRecNo then
  begin
    FDataSet.DoBeforeScroll;
    InternalFirst;
    SkipRecords(Value);
    FDataSet.Resync([rmCenter]);
    FDataSet.DoAfterScroll;
  end;
end;

function TLXDataSetImpl.GetRecNo: Integer;
var
  RecordBuffer: PLXRecordBuffer;
begin
  FDataSet.CheckActive;
  if GetFieldsRecordBuffer(RecordBuffer, FDataSet.State) then
    Result := RecordBuffer^.SequenceNumber else
    Result := 0;
end;

function TLXDataSetImpl.GetRecordSize: Word;
begin
  Result := FRecordSize;
end;

function TLXDataSetImpl.UpdateStatus: TUpdateStatus;
var
  RecordBuffer: PLXRecordBuffer;
begin
  FDataSet.CheckActive;
  if GetFieldsRecordBuffer(RecordBuffer, FDataSet.State) then
    Result := RecordBuffer^.UpdateStatus else
    Result := usUnmodified;
end;

function TLXDataSetImpl.IsSequenced: Boolean;
begin
  Result := not IsFiltered and IsCursorOpen and IsAllFetched;
end;

{ Bookmarks Support }

function TLXDataSetImpl.GetBookmarkSize: Integer;
begin
  Result := SizeOf(PLXRecordBuffer);
end;

procedure TLXDataSetImpl.GetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
var
  RecordBuffer: PLXRecordBuffer;
begin
  RecordBuffer := PPRecordBuffer(Buffer)^;
  PPRecordBuffer(Data)^ := RecordBuffer;
end;

{ this function used only in DataSet.Insert method }

procedure TLXDataSetImpl.SetBookmarkData(Buffer: TRecordBuffer; Data: Pointer);
var
  InsertedRecord: PLXRecordBuffer;
  NextRecord: PLXRecordBuffer;
begin
  InsertedRecord := PPRecordBuffer(Buffer)^;
  NextRecord := PPRecordBuffer(Data)^;
  InsertedRecord^.Next := NextRecord;
  InsertedRecord^.Prior := NextRecord^.Prior;
  InsertedRecord^.SequenceNumber := NextRecord^.SequenceNumber;
end;

function TLXDataSetImpl.GetBookmarkFlag(Buffer: TRecordBuffer): TBookmarkFlag;
var
  RecordBuffer: PLXRecordBuffer;
begin
  RecordBuffer := PPRecordBuffer(Buffer)^;
  if RecordBuffer^.Prior^.Next <> RecordBuffer then
    Result := bfInserted else
    Result := bfCurrent;
end;

{ this function used only in DataSet.Insert and DataSet.Append method }

procedure TLXDataSetImpl.SetBookmarkFlag(Buffer: TRecordBuffer;
  Value: TBookmarkFlag);
var
  InsertedRecord: PLXRecordBuffer;
begin
  InsertedRecord := PPRecordBuffer(Buffer)^;
  case Value of
    bfBOF:
      begin
        InsertedRecord^.Next := FBOFRecord.Next;
        InsertedRecord^.Prior := @FBOFRecord;
        InsertedRecord^.SequenceNumber := 1;
      end;
    bfEOF:
      begin
        InternalLast;
        InsertedRecord^.Next := @FEOFRecord;
        InsertedRecord^.Prior := FEOFRecord.Prior;
        InsertedRecord^.SequenceNumber := GetRecordCount + 1;
      end;
  end;
end;

procedure TLXDataSetImpl.InternalGotoBookmark(Bookmark: Pointer);
begin
  if BookmarkValid(Bookmark) then
    FCurrentRecord := PPRecordBuffer(Bookmark)^ else
    DatabaseError(SInvalidBookmark, FDataSet);
end;

procedure TLXDataSetImpl.InternalSetToRecord(Buffer: TRecordBuffer);
begin
  FCurrentRecord := PPRecordBuffer(Buffer)^;
end;

function TLXDataSetImpl.BookmarkValid(Bookmark: TBookmark): Boolean;
var
  RecordBuffer: PLXRecordBuffer;
begin
  Result := (Bookmark <> nil) and (FRecords <> nil);
  if Result then
    try
      RecordBuffer := PPRecordBuffer(Bookmark)^;
      Result := (RecordBuffer = @FBOFRecord) or (RecordBuffer = @FEOFRecord) or
        FRecords.IsValidRecord(RecordBuffer);
    except
      Result := False;
    end;
end;

function TLXDataSetImpl.CompareBookmarks(Bookmark1, Bookmark2: TBookmark): Integer;
const
  RetCodes: array [Boolean, Boolean] of ShortInt = ((0, 1), (-1, 2));
begin
  Result := RetCodes[BookmarkValid(Bookmark1), BookmarkValid(Bookmark2)];
  if Result = 2 then
  begin
    Result := PPRecordBuffer(Bookmark1)^^.SequenceNumber - PPRecordBuffer(Bookmark2)^^.SequenceNumber;
    if Result < 0 then
      Result := -1
    else if Result > 0 then
      Result := 1;
  end;
end;

{ Filter / Locate / Find }

procedure TLXDataSetImpl.SetStatusFilter(Value: TUpdateStatusSet);
begin
  if Value <> FStatusFilter then
  begin
    FStatusFilter := Value;
    if FDataSet.Active then
      FDataSet.First;
  end;
end;

function TLXDataSetImpl.IsInStatusFilter(UpdateStatus: TUpdateStatus): Boolean;
begin
  if FStatusFilter = [] then
    Result := UpdateStatus in [usModified, usInserted, usUnmodified] else
    Result := UpdateStatus in FStatusFilter;
end;

function TLXDataSetImpl.LocateRecord(const KeyFields: string;
  KeyValues: Variant; Options: TLocateOptions; FromFirst: Boolean): Boolean;

  function NormalizeKeyValues(KeyFieldList: TList): Boolean;
  var
    I: Integer;
    VarType: TVarType;
  begin
    if not LXVarIsArray(KeyValues) then
      KeyValues := LXVarArrayOf([KeyValues]);
    Result := False;
    try
      for I := 0 to LXVarArrayHighBound(KeyValues, 1) do
      begin
        if LXVarIsNull(KeyValues[I]) then Exit;
        VarType := LXFieldTypeVarMap[TField(KeyFieldList[I]).DataType];
        if VarType <> varString then
          KeyValues[I] := LXVarAsType(KeyValues[I], VarType) else
        begin
          if loCaseInsensitive in Options then
            KeyValues[I] := AnsiUpperCase(TrimRight(LXVarToStr(KeyValues[I]))) else
            KeyValues[I] := TrimRight(LXVarToStr(KeyValues[I]));
        end;
      end;
      Result := True;
    except
      on EVariantError do;
    end;
  end;

  function IsFieldMatchesLocate(Field: TField; const KeyValue: Variant): Boolean;
  var
    FieldValue: Variant;
    FieldStr: string;
  begin
    Result := False;
    FieldValue := Field.Value;
    if not LXVarIsNull(FieldValue) then
      if LXVarType(FieldValue) <> varString then
        Result := FieldValue = KeyValue else
      begin
        FieldStr := TrimRight(LXVarToStr(FieldValue));
        if loCaseInsensitive in Options then
          FieldStr := AnsiUpperCase(FieldStr);
        if loPartialKey in Options then
          Result := AnsiPos(KeyValue, FieldStr) = 1 else
          Result := KeyValue = FieldStr;
      end;
  end;

var
  KeyFieldList: TList;
  I: Integer;
begin
  FDataSet.CheckBrowseMode;

  Result := False;
  KeyFieldList := TList.Create;
  try
    FDataSet.GetFieldList(KeyFieldList, KeyFields);
    if not NormalizeKeyValues(KeyFieldList) then Exit;
    FDataSet.SetTempState(dsFilter);
    try
      if FromFirst then
        InternalFirst else
        FDataSet.UpdateCursorPos;
      FDataSet.CursorPosChanged;
      while not Result and (GetRecord(gmNext) = grOK) do
      begin
        FFilterBuffer := FCurrentRecord;
        Result := True;
        for I := 0 to KeyFieldList.Count - 1 do
          if not IsFieldMatchesLocate(KeyFieldList[I], KeyValues[I]) then
          begin
            Result := False;
            Break;
          end;
      end;
    finally
      FDataSet.RestoreState(dsBrowse);
    end;
  finally
    KeyFieldList.Free;
  end;
end;

procedure TLXDataSetImpl.SetCurrentAsFiltered;
begin
  FFilterBuffer := FCurrentRecord;
end;

function TLXDataSetImpl.Locate(const KeyFields: string;
  const KeyValues: Variant; Options: TLocateOptions; FromFirst: Boolean): Boolean;
begin
  FDataSet.DoBeforeScroll;
  Result := LocateRecord(KeyFields, KeyValues, Options, FromFirst);
  if Result then
  begin
    FDataSet.Resync([rmExact, rmCenter]);
    FDataSet.DoAfterScroll;
  end;
end;

function TLXDataSetImpl.Lookup(const KeyFields: string;
  const KeyValues: Variant; const ResultFields: string): Variant;
begin
  if not LocateRecord(KeyFields, KeyValues, [], True) then
    Result := LXNull else
  begin
    if FDataSet.AutoCalcFields then
      FDataSet.GetCalcFields(@FCurrentRecord);
    FDataSet.SetTempState(dsFilter);
    try
      Result := FDataSet.FieldValues[ResultFields];
    finally
      FDataSet.RestoreState(dsBrowse);
    end;
  end;
end;


{ TLXDataSetUpdateObject }

destructor TLXDataSetUpdateObject.Destroy;
begin
  if FDataSetImpl <> nil then
    FDataSetImpl.FUpdateObject := nil;
  inherited;
end;

procedure TLXDataSetUpdateObject.SetDataSetImpl(DataSetImpl: TLXDataSetImpl);
begin
  if FDataSetImpl <> nil then
    FDataSetImpl.FUpdateObject := nil;
  FDataSetImpl := DataSetImpl;
  if FDataSetImpl <> nil then
    FDataSet := FDataSetImpl.FDataSet else
    FDataSet := nil;
end;


end.